use std::fmt::format;
use std::fs;
use std::path::{Path, PathBuf};
use std::io::Write;
use serde::{Serialize, Deserialize};
use anyhow::{bail, Context, Error, Result};
use serde_json::{Map, Value};
use walkdir::{DirEntry, WalkDir};


fn main() -> Result<()> {
    let tag = "[oh-ndk]";
    let out_path = PathBuf::from(std::env::var("OUT_DIR").unwrap());
    let sdk_dir = get_sdk_dir();
    let sysroot = sdk_dir.join("sysroot");


    let root_dir = sysroot.join("usr");
    let include_dir = root_dir.join("include");
    let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
    let arch_dir = format!("{arch}-linux-ohos");
    let lib_dir = root_dir.join("lib").join(arch_dir.as_str());
    let include_arch_dir = include_dir.join(arch_dir.as_str());

    if !include_dir.exists() {
        bail!("{tag} {}: 未找到SDK。", include_dir.display());
    }


    let sdk_info = get_sdk_info(&sdk_dir)?;

    {
        let api_version: usize = sdk_info.apiVersion.parse()?;
        let dest_path = out_path.join("constants.rs");
        let mut f = fs::File::create(&dest_path)?;
        write!(&mut f, "const OH_NDK_API_VERSION: usize = {};", api_version)?;
    }
    println!("{tag} found.");
    println!("{tag} include: {}", include_dir.display());
    println!("{tag} lib_dir: {}", lib_dir.display());
    println!("{tag} sdk: {:?}", sdk_info);
    link_libraries(lib_dir.as_path())?;

    let headers = read_headers(&sdk_dir, &include_dir)?;
    let warp_h = gen_warp_h(headers.as_slice(), &out_path)?;
    let cpp_include = sdk_dir
        .join("llvm").join("include").join("c++").join("v1");

    let cfg = bindgen::Builder::default()
        // The input header we would like to generate
        // bindings for.
        .header(warp_h.display().to_string())
        .clang_arg(format!("-I{}", include_arch_dir.display()))
        .clang_arg(format!("-I{}", include_dir.display()))
        .clang_arg(format!("--sysroot={}", sysroot.display()))
        .clang_arg("-D__MUSL__")
        .clang_arg(format!("-I{}", cpp_include.display()))
        .size_t_is_usize(false)
        ;

    let bindings = cfg
        // Finish the builder and generate the bindings.
        .generate()?;

    // Write the bindings to the $OUT_DIR/bindings.rs file.
    bindings
        .write_to_file(out_path.join("bindings.rs"))?;

    Ok(())
}


fn get_sdk_dir() -> PathBuf {
    if let Ok(env) = std::env::var("OHOS_SDK_NATIVE") {
        return PathBuf::from(env);
    }
    if let Some(d) = find_default_dir(true) {
        return d;
    }

    if let Some(d) = find_default_dir(false) {
        return d;
    }


    panic!("未找到 OH SDK")
}

fn get_usr_local_dir() -> PathBuf {
    let base_dirs = directories::BaseDirs::new().unwrap();
    let data_local_dir = base_dirs.data_local_dir();
    data_local_dir.into()
}

fn find_default_dir(is_harmony_os: bool) -> Option<PathBuf> {
    let mut dir = get_usr_local_dir();
    dir = dir.join(if is_harmony_os {
        "Huawei"
    } else {
        "OpenHarmony"
    });
    dir = dir.join("Sdk");
    if is_harmony_os {
        dir = dir.join("openharmony");
    }

    match dir.read_dir() {
        Err(_) => {
            return None;
        }
        Ok(paths) => {
            for path in paths {
                if let Ok(p) = path {
                    let native = p.path().join("native");
                    if native.exists() {
                        return Some(native);
                    }
                }
            }
        }
    }
    None
}

#[allow(non_snake_case)]
#[derive(Serialize, Deserialize, Debug)]
struct Meta {
    metaVersion: String,
}

#[allow(non_snake_case)]
#[derive(Serialize, Deserialize, Debug)]
struct SDKInfo {
    apiVersion: String,
    displayName: String,
    meta: Meta,
    path: String,
    releaseType: String,
    version: String,
}

fn get_sdk_info(root: &PathBuf) -> Result<SDKInfo> {
    let data = root.join("oh-uni-package.json");
    let data = fs::read_to_string(&data).with_context(||
        format!("oh-ndk 无法读取版本信息：{}", data.display()))?;
    let info: SDKInfo = serde_json::from_str(data.as_str())?;
    Ok(info)
}

fn read_headers(root: &PathBuf, include: &PathBuf) -> Result<Vec<String>> {
    let data = root.join("nativeapi_syscap_config.json");
    let data = fs::read_to_string(&data).with_context(||
        format!("oh-ndk 无法读取版本信息：{}", data.display()))?;
    let mut out = vec![
        "napi/native_api.h".to_string(),
        "ace/xcomponent/native_interface_xcomponent.h".to_string(),
    ];
    let v: Map<String, Value> = serde_json::from_str(&data)?;

    let mut h_names = vec![];

    for (name, headers) in v {
        println!("{}: {}", name, headers);
        if let Some(h) = headers.as_array() {
            for one in h {
                if let Some(header) = one.as_str() {
                    let path = PathBuf::from(header);
                    if let Some(p) = path.file_name() {
                        h_names.push(p.to_string_lossy().to_string());
                    }
                }
            }
        }
    }


    for h in h_names {
        let h_path = find_file(include, h.as_ref())
            .ok_or(Error::msg("not found"))?;
        let mut head = h_path.display().to_string();
        let t = format!("{}\\", &include.display());
        head = head.replace(&t, "");

        out.push(head);
    }


    Ok(out)
}


fn find_file(dir: &Path, name: &str) -> Option<PathBuf> {
    if dir.is_dir() {
        for entry in fs::read_dir(dir).ok()? {
            let path = entry.ok()?.path();
            if path.is_dir() {
                if let Some(file) = find_file(&path, name) {
                    return Some(file);
                }
            } else if path.file_name().map_or(false, |name1| name == name1) {
                return Some(path);
            }
        }
    }
    None
}

fn gen_warp_h(headers: &[String], output: &PathBuf) -> Result<PathBuf> {
    let warp_h = output.join("warp.hpp");
    if warp_h.exists() {
        fs::remove_file(warp_h.as_path())?;
    }
    let mut f = fs::File::create(warp_h.as_path())?;
    for header in headers {
        write!(f, "#include<{}>\n", header)?;
    }

    Ok(warp_h)
}
fn get_lib_name(file: &DirEntry)->String{
    let mut name = file.path().file_name().unwrap().to_string_lossy().replace(".so", "");
    name = name.replace(".a", "");
    name = name.replace("lib", "");
    name
}

fn link_libraries(lib_dir: &Path) -> Result<()> {
    let dyn_libs: Vec<String> = WalkDir::new(lib_dir)
        .into_iter()
        .filter_map(|e| e.ok())
        .filter_map(|file| {
            if let Some(ext) = file.path().extension() {
                if ext == "so" {
                    return  Some(get_lib_name(&file));
                }
            }
            None
        })
        .collect();

    let static_libs:Vec<String> = WalkDir::new(lib_dir)
        .into_iter()
        .filter_map(|e| e.ok())
        .filter_map(|file|{
            if let Some(ext) = file.path().extension() {
                if ext == "a" {
                    let lib_name = get_lib_name(&file);
                    if !dyn_libs.contains(&lib_name){
                        return  Some(lib_name);
                    }
                }
            }
            None
        })
        .collect();

    for lib in dyn_libs{
        println!("cargo:rustc-link-lib=dylib={}", lib);
    }
    for lib in static_libs{
        println!("cargo:rustc-link-lib={}", lib);
    }

    Ok(())
}
